/**
 * Copyright 2021 xpstem.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.xpstem.lelink.operation;

import com.xpstem.lelink.enums.OpCode;
import com.xpstem.lelink.type.*;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;

/**
 * EV3 communication operations.
 * @author billy zhang
 */
public class EV3CommOpFactory {

    private static final short CMD_GET_ON_OFF = 0x01;
    private static final short CMD_GET_VISIBLE = 0x02;
    private static final short CMD_GET_RESULT = 0x04;
    private static final short CMD_GET_PIN = 0x05;
    private static final short CMD_SEARCH_ITEMS = 0x08;
    private static final short CMD_SEARCH_ITEM = 0x09;
    private static final short CMD_FAVOUR_ITEMS = 0x0A;
    private static final short CMD_FAVOUR_ITEM = 0x0B;
    private static final short CMD_GET_ID = 0x0C;
    private static final short CMD_GET_BRICKNAME = 0x0D;
    private static final short CMD_GET_NETWORK = 0x0E;
    private static final short CMD_GET_PRESENT = 0x0F;
    private static final short CMD_GET_ENCRYPT = 0x10;
    private static final short CMD_GET_INCOMMING = 0x11;

    private static final short CMD_SET_ON_OFF = 0x01;
    private static final short CMD_SET_VISIBLE = 0x02;
    private static final short CMD_SET_SEARCH = 0x03;
    private static final short CMD_SET_PIN = 0x05;
    private static final short CMD_SET_PASSKEY = 0x06;
    private static final short CMD_SET_CONNECTION = 0x07;
    private static final short CMD_SET_BRICKNAME = 0x08;
    private static final short CMD_SET_MOVEUP = 0x09;
    private static final short CMD_SET_MOVEDOWN = 0x0A;
    private static final short CMD_SET_ENCRYPT = 0x0B;
    private static final short CMD_SET_SSID = 0x0C;


    private EV3CommOpFactory() { }

    /**
     * This function enables the program to verify and wait if communication is busy.
     *
     * The hardware parameter can take the following values:
     * 1: USB communication (Client interface)
     * 2: Bluetooth communication interface
     * 3: WiFi communication interface
     *
     * @param hardware (Data8) HARDWARE, See further documentation below.
     * @param name (Data8) *pNAME - Name of the remote/own device (0 = Own adapter status)
     */
    public static EV3Operation opComReady(
            Data8 hardware,
            String name) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        LCEncoder.from(hardware).lcx(outputStream);
        LCEncoder.from(name).lcs(outputStream);
        byte[] opData = outputStream.toByteArray();

        EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.COM_READY, opData);
        return new EV3Operation(opPkg );
    }

    /**
     * This function enables the program to test if communication is busy.
     *
     * The hardware parameter can take the following values:
     * 1: USB communication (Client interface)
     * 2: Bluetooth communication interface
     * 3: WiFi communication interface
     *
     * @param hardware (Data8) HARDWARE, See further documentation below.
     * @param name (Data8) *pNAME - Name of the remote/own device (0 = Own adapter status)
     */
    public static EV3Operation opComTest(
            Data8 hardware,
            String name,
            ReturnParam<Data8> busy) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        LCEncoder.from(hardware).lcx(outputStream);
        LCEncoder.from(name).lcs(outputStream);
        GVEncoder.from(busy.getIndex()).gvx(outputStream);
        byte[] opData = outputStream.toByteArray();

        EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.COM_TEST, opData, busy.getSize());
        return new EV3Operation(opPkg, Arrays.asList(busy) );
    }

    /**
     * Communication get entry
     */
    public static class OpComSet {

        public static EV3Operation setBrickname(String name) throws IOException {

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

            LCEncoder.from(CMD_SET_BRICKNAME).lc0(outputStream);
            LCEncoder.from(name).lcs(outputStream);
            byte[] opData = outputStream.toByteArray();

            EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.COM_SET, opData);
            return new EV3Operation(opPkg, "COM_SET_Brickname");
        }
    }

    /**
     * Communication get entry
     */
    public static class OpComGet {

        /**
         * Get the name of the brick
         * @param length
         * @return param0
         */
        public static EV3Operation getBrickname(
                Data8 length, ReturnParam<DataS> name) throws IOException {

            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

            LCEncoder.from(CMD_GET_BRICKNAME).lc0(outputStream);
            LCEncoder.from(length).lcx(outputStream);
            GVEncoder.from(name.getIndex()).gvx(outputStream);
            byte[] opData = outputStream.toByteArray();

            EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.COM_GET, opData, name.getSize());
            return new EV3Operation(opPkg, Arrays.asList(name), "COM_GET_Brickname");
        }
    }

    /**
     * This function enables opening a mailbox to be used within the running program.
     * Maximum mailbox size is 250 bytes. I.e. if type is string (DataS) then thee can only be
     * 1 string of maximum 250 bytes (Incl. zero termination), or of array (DataA), then array
     * size cannot be larger than 250 bytes.
     *
     * Type data supported:
     * 0x00 : Data8 8-bit integer value
     * 0x01 : Data16 16-bit integer value
     * 0x02 : Data32 32-bit integer value
     * 0x03 : DataF Floating point data
     * 0x04 : DataS Zero terminated string
     * 0x05 : DataA Array handle
     * 0x06 : DataV Variable type
     *
     * @param no (Data8) NO – Reference ID for mailbox. Maximum number of mailboxes is 30.
     * @param boxName (Data8) BOXNAME – Zero terminated string with mailbox name
     * @param type (Data8) TYPE – Data type used within mailbox, see details below.
     * @param fifosize (Data8) FIFOSIZE – Not used at this point
     * @param values (Data8) VALUES – Number of data entries (data specified by type above)
     */
    public static EV3Operation opMailboxOpen(
            Data8 no,
            String boxName,
            Data8 type,
            Data8 fifosize,
            Data8 values) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        LCEncoder.from(no).lc0(outputStream);
        LCEncoder.from(boxName).lcs(outputStream);
        LCEncoder.from(type).lcx(outputStream);
        LCEncoder.from(fifosize).lcx(outputStream);
        LCEncoder.from(values).lcx(outputStream);
        byte[] opData = outputStream.toByteArray();

        EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.MAILBOX_OPEN, opData);
        return new EV3Operation(opPkg );
    }

    /**
     * This function enables a running program to write data to another bricks mailbox.
     * When using data type DataS, then a zero terminated string is expected. If data type is
     * DataA, then number of bytes to be send is equal to array size.
     * If brickname is left empty, then all connected devices will receive the mailbox
     * message.
     *
     * @param brickName
     * @param hardware
     * @param boxName
     * @param type
     * @param values
     */
    public static EV3Operation opMailboxWrite(
            String brickName,
            Data8 hardware,
            String boxName,
            Data8 type,
            Data8 values,
            EV3Type[] valueArray) throws IOException {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        LCEncoder.from(brickName).lcs(outputStream);
        LCEncoder.from(hardware).lcx(outputStream);
        LCEncoder.from(boxName).lcs(outputStream);
        LCEncoder.from(type).lcx(outputStream);
        LCEncoder.from(values).lcx(outputStream);
        switch (type.getValue()) {
            case 0x00:
                for (int i=0; i<valueArray.length; i++) {
                    LCEncoder.from((Data8) valueArray[i]).lc1(outputStream);
                }
                break;
            case 0x01:
                for (int i=0; i<valueArray.length; i++) {
                    LCEncoder.from((Data16) valueArray[i]).lc2(outputStream);
                }
                break;
            case 0x02:
                for (int i=0; i<valueArray.length; i++) {
                    LCEncoder.from((Data32) valueArray[i]).lc4(outputStream);
                }
                break;
            case 0x04:
                for (int i=0; i<valueArray.length; i++) {
                    LCEncoder.from(((DataS) valueArray[i]).getValue()).lcs(outputStream);
                }
                break;
            default:
                break;
        }
        byte[] opData = outputStream.toByteArray();

        EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.MAILBOX_WRITE, opData);
        return new EV3Operation(opPkg );
    }

    /**
     * This function enables a running program read data from a specific mailbox.
     *
     * @param no (Data8) NO – Message box ID
     * @param length (Data8) LENGTH – Maximum bytes to be read
     * @param values (Data8) VALUES – Number of value to read
     * @param valueArray (Depending on mailbox settings) VALUE – Data from the message box
     */
    public static EV3Operation opMailboxRead(
            Data8 no,
            Data8 length,
            Data8 values,
            ReturnParam<?> valueArray) throws IOException {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        LCEncoder.from(no).lc0(outputStream);
        LCEncoder.from(length).lcx(outputStream);
        LCEncoder.from(values).lcx(outputStream);
        GVEncoder.from(valueArray.getIndex()).gvx(outputStream);

        byte[] opData = outputStream.toByteArray();

        EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.MAILBOX_READ, opData, valueArray.getSize());
        return new EV3Operation(opPkg, Arrays.asList(valueArray) );

    }

    /**
     * This function enables a running program to test if a new message have been
     * received.
     *
     * @param no (Data8) NO – Message box ID
     * @param busy (Data8) BUSY – Busy equal TRUE if no new message have been received
     */
    public static EV3Operation opMailboxTest(
            Data8 no,
            ReturnParam<Data8> busy) throws IOException {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        LCEncoder.from(no).lc0(outputStream);
        GVEncoder.from(busy.getIndex()).gvx(outputStream);

        byte[] opData = outputStream.toByteArray();

        EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.MAILBOX_TEST, opData, busy.getSize());
        return new EV3Operation(opPkg, Arrays.asList(busy) );

    }

    /**
     * This function enables a running program to wait until a message have been received
     * within the specified mail box.
     *
     * @param no (Data8) NO – Message box ID
     */
    public static EV3Operation opMailboxReady(
            Data8 no) throws IOException {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        LCEncoder.from(no).lc0(outputStream);
        byte[] opData = outputStream.toByteArray();

        EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.MAILBOX_READY, opData);
        return new EV3Operation(opPkg );

    }

    /**
     * This function enables a running program to close an open mail box.
     *
     * @param no (Data8) NO – Message box ID
     */
    public static EV3Operation opMailboxClose(
            Data8 no) throws IOException {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        LCEncoder.from(no).lc0(outputStream);
        byte[] opData = outputStream.toByteArray();

        EV3OperationPackage opPkg = new EV3OperationPackage(OpCode.MAILBOX_CLOSE, opData);
        return new EV3Operation(opPkg );
    }

}
